package com.xnyzc.lhy.gateway.config;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.xnyzc.lhy.common.util.CheckUtil;
import com.xnyzc.lhy.common.util.ConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;
import redis.clients.jedis.JedisPoolConfig;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @Description:
 * @Author: scootXin
 * @Date: 2018/12/21 10:14
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 前缀配置
     */
    @Value("${global.pang.rediskey}")
    private String keyPrefix;

    /**
     * 连接池公用配置
     */
    @Value("${spring.jedis.pool.other.maxTotal}")
    private int poolMaxTotal;

    @Value("${spring.jedis.pool.other.maxIdle}")
    private int poolMaxIdle;

    @Value("${spring.jedis.pool.minIdle}")
    private int poolMinIdle;

    @Value("${spring.jedis.pool.maxWaitMillis}")
    private int poolMaxWaitMillis;

    @Value("${spring.jedis.pool.numTestsPerEvictionRun}")
    private int poolNumTestsPerEvictionRun;

    @Value("${spring.jedis.pool.timeBetweenEvictionRunsMillis}")
    private int poolTimeBetweenEvictionRunsMillis;

    @Value("${spring.jedis.pool.minEvictableIdleTimeMillis}")
    private int poolMinEvictableIdleTimeMillis;

    @Value("${spring.jedis.pool.softMinEvictableIdleTimeMillis}")
    private int poolSoftMinEvictableIdleTimeMillis;

    @Value("${spring.jedis.pool.testOnBorrow}")
    private boolean poolTestOnBorrow;

    @Value("${spring.jedis.pool.testWhileIdle}")
    private boolean poolTestWhileIdle;

    @Value("${spring.jedis.pool.testOnReturn}")
    private boolean poolTestOnReturn;

    @Value("${spring.jedis.pool.blockWhenExhausted}")
    private boolean poolBlockWhenExhausted;

    @Value("${spring.jedis.pool.timeout}")
    private int poolTimeout;

    /**
     * lru缓存配置
     */
    @Value("${spring.jedis.lru.host}")
    private String lruHost;
    @Value("${spring.jedis.lru.port}")
    private int lruPort;
    @Value("${spring.jedis.lru.password}")
    private String lruPassword;

    /**
     * 驻留缓存配置
     */
    @Value("${spring.jedis.lru.host}")
    private String persistenceHost;
    @Value("${spring.jedis.lru.port}")
    private int persistencePort;
    @Value("${spring.jedis.lru.password}")
    private String persistencePassword;

    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(poolMaxIdle);
        poolConfig.setMinIdle(poolMinIdle);
        poolConfig.setMaxWaitMillis(poolMaxWaitMillis);
        poolConfig.setMaxTotal(poolMaxTotal);

        poolConfig.setTestOnBorrow(poolTestOnBorrow);
        poolConfig.setTestOnReturn(poolTestOnReturn);
        poolConfig.setTestWhileIdle(poolTestWhileIdle);
        poolConfig.setNumTestsPerEvictionRun(poolNumTestsPerEvictionRun);
        poolConfig.setTimeBetweenEvictionRunsMillis(poolTimeBetweenEvictionRunsMillis);
        poolConfig.setMinEvictableIdleTimeMillis(poolMinEvictableIdleTimeMillis);
        poolConfig.setSoftMinEvictableIdleTimeMillis(poolSoftMinEvictableIdleTimeMillis);
        poolConfig.setBlockWhenExhausted(poolBlockWhenExhausted);

        return poolConfig;
    }

    public RedisConnectionFactory lruRedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        //设置redis服务器的host或者ip地址
        redisStandaloneConfiguration.setHostName(lruHost);
        //设置默认使用的数据库
        redisStandaloneConfiguration.setDatabase(0);
        //设置密码
        redisStandaloneConfiguration.setPassword(RedisPassword.of(lruPassword));
        //设置redis的服务的端口号
        redisStandaloneConfiguration.setPort(lruPort);
        //单机配置 + 客户端配置 = jedis连接工厂
        return getJedisConnectionFactory(redisStandaloneConfiguration);
    }

    public RedisConnectionFactory persistenceRedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        //设置redis服务器的host或者ip地址
        redisStandaloneConfiguration.setHostName(persistenceHost);
        //设置默认使用的数据库
        redisStandaloneConfiguration.setDatabase(0);
        //设置密码
        redisStandaloneConfiguration.setPassword(RedisPassword.of(persistencePassword));
        //设置redis的服务的端口号
        redisStandaloneConfiguration.setPort(persistencePort);

        //单机配置 + 客户端配置 = jedis连接工厂
        return getJedisConnectionFactory(redisStandaloneConfiguration);
    }

    private JedisConnectionFactory getJedisConnectionFactory(RedisStandaloneConfiguration redisStandaloneConfiguration) {
        //获得默认的连接池构造器(怎么设计的，为什么不抽象出单独类，供用户使用呢)
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)
                JedisClientConfiguration.builder();
        //指定jedisPoolConifig来修改默认的连接池构造器（真麻烦，滥用设计模式！）
        jpcb.poolConfig(jedisPoolConfig());
        //通过构造器来构造jedis客户端配置
        JedisClientConfiguration jedisClientConfiguration = jpcb.build();

        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
    }

    @Primary
    @Bean("redisTemplate")
    public RedisTemplate redisTemplate() {
        RedisTemplate redisTemplate = createInitRedisTemplate();

        redisTemplate.setConnectionFactory(lruRedisConnectionFactory());

        return redisTemplate;
    }

    @Bean("persistenceRedisTemplate")
    public RedisTemplate persistenceRedisTemplate() {
        RedisTemplate redisTemplate = createInitRedisTemplate();

        redisTemplate.setConnectionFactory(persistenceRedisConnectionFactory());

        return redisTemplate;
    }

    /**
     * 初始构造方法
     *
     * @return
     */
    private RedisTemplate createInitRedisTemplate() {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setKeySerializer(pangRedisKeySerializer());
        redisTemplate.setStringSerializer(pangRedisStringSerializer());
        redisTemplate.setHashKeySerializer(pangRedisStringSerializer());

        redisTemplate.setHashValueSerializer(pangValSerializer());
        redisTemplate.setValueSerializer(pangValSerializer());
        return redisTemplate;
    }

    @Bean("pangValSerializer")
    public RedisSerializer<Object> pangValSerializer() {
        return new FastJsonRedisSerializer(Object.class);
    }

    @Bean("pangRedisStringSerializer")
    public RedisSerializer<Object> pangRedisStringSerializer() {
        return new RedisSerializer<Object>() {

            private final Charset charset = StandardCharsets.UTF_8;

            @Nullable
            @Override
            public byte[] serialize(@Nullable Object s) throws SerializationException {
                if (CheckUtil.objIsEmpty(s)) {
                    return null;
                }

                String str = ConvertUtil.toString(s);
                return str.getBytes(charset);
            }

            @Nullable
            @Override
            public Object deserialize(@Nullable byte[] bytes) throws SerializationException {
                if (CheckUtil.objIsEmpty(bytes)) {
                    return null;
                }

                String saveKey = new String(bytes, charset);
                return saveKey;
            }
        };
    }

    @Bean("pangRedisKeySerializer")
    public RedisSerializer<Object> pangRedisKeySerializer() {
        return new RedisSerializer<Object>() {

            private final Charset charset = StandardCharsets.UTF_8;

            @Nullable
            @Override
            public byte[] serialize(@Nullable Object s) throws SerializationException {
                if (CheckUtil.objIsEmpty(s)) {
                    return null;
                }

                String str = ConvertUtil.toString(s);

                if (str.startsWith(keyPrefix)) {
                    return str.getBytes(charset);
                }
                else {
                    String key = keyPrefix + s;
                    return (key == null ? null : key.getBytes(charset));
                }
            }

            @Nullable
            @Override
            public Object deserialize(@Nullable byte[] bytes) throws SerializationException {
                String saveKey = new String(bytes, charset);
                int indexOf = saveKey.indexOf(keyPrefix);
                if (indexOf > 0) {
                    log.info("key缺少前缀" + saveKey);
                }
                else {
                    saveKey = saveKey.substring(indexOf);
                }

                return (saveKey.getBytes() == null ? null : saveKey);
            }
        };
    }

}
